import 'babel-polyfill';

import $ from 'jquery';
import Vue from 'vue';
import io from 'socket.io-client';
import utilTools from './utils';
import qrcode from 'qrcode';

const keyNames = [
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //000-009
    '',         '',         '',         'enter',    '',         '',         'shift',    'ctrl',     'alt',      '',         //010-019
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //020-029
    '',         '',         'space',    '',         '',         '',         '',         'left',     'up',       'right',    //030-039
    'down',     '',         '',         '',         '',         '',         '',         '',         '',         '',         //040-049
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //050-059
    '',         '',         '',         '',         '',         'a',        'b',        'c',        'd',        'e',        //060-069
    'f',        'g',        'h',        'i',        'j',        'k',        'l',        'm',        'n',        'o',        //070-079
    'p',        'q',        'r',        's',        't',        'u',        'v',        'w',        'x',        'y',        //080-089
    'z',        '',         '',         '',         '',         '',         '',         '',         '',         '',         //090-099
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //100-109
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //110-119
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //120-129
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //130-139
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //140-149
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //150-159
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //160-169
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //170-179
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //180-189
    '',         '',         '',         '',         '',         '',         '',         '',         '',         '',         //190-199
];
const keyStates = keyNames.map(v => false);

const getEventName = (type, name) => `${ type }.${ name }`;

const clone = obj => {
    return JSON.parse(JSON.stringify(obj));
};

const clear = value => {
    if (typeof value === 'object' && value !== null) {
        for (let [k, v] of Object.entries(value)) {
            if (!k.startsWith('~')) {
                value[k] = clear(v);
            }
        }
        for (let [k, v] of Object.entries(value)) {
            if (k.startsWith('~') && v === true) {
                delete value[k];
            }
        }
    }
    return value;
};

const setProp = (obj, prop, value) => {
    if (Array.isArray(obj)) {
        obj.splice(prop, 1, clear(value));
    } else {
        Vue.set(obj, prop, clear(value));
    }
};

const set = (obj, prop, value) => {
    if (typeof obj === 'undefined') return;
    if (prop.startsWith('~')) {
        if (value === true) {
            Vue.delete(obj, prop.substring(1));
        }
    } else {
        let old = obj[prop];
        if (typeof old === 'undefined') {
            setProp(obj, prop, value);
        } else {
            if (old !== value) {
                if (typeof value === 'object' && value !== null) {
                    if (typeof old !== 'object') {
                        setProp(obj, prop, value);
                    } else {
                        if (Array.isArray(old) && Array.isArray(value)) {
                            old.splice(value.length);
                        }
                        for (let name in value) {
                            if (value.hasOwnProperty(name)) {
                                set(old, name, value[name]);
                            }
                        }
                    }
                } else {
                    setProp(obj, prop, value);
                }
            }
        }
    }
};

const append = (array, items) => {
    items.forEach(item => {
        if (item === '~') {
            array.splice(array.length - 1, 1);
        } else if (item === '*') {
            array.splice(0);
        } else {
            array.push(item);
        }
    });
};

const getTouch = (event, target, index) => {
    let filter = touch => $(target).closest(touch.target).length > 0 || $(touch.target).closest(target).length > 0;
    if (event.targetTouches) {
        let touches = [...event.targetTouches].filter(filter);
        if (typeof index === 'function') {
            let touch = touches.find(index);
            if (touch) return touch;
        } else if (touches.length > index) {
            return touches[index];
        }
    }
    if (event.changedTouches) {
        let touches = [...event.changedTouches].filter(filter);
        if (typeof index === 'function') {
            let touch = touches.find(index);
            if (touch) return touch;
        } else if (touches.length > index) {
            return touches[index];
        }
    }
    return event;
};

const audioContext = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext)();
// const Base64Binary = {
//     _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
//
//     /* will return a  Uint8Array type */
//     decodeArrayBuffer: function(input) {
//         let bytes = (input.length/4) * 3;
//         let ab = new ArrayBuffer(bytes);
//         this.decode(input, ab);
//
//         return ab;
//     },
//
//     removePaddingChars: function(input){
//         let lkey = this._keyStr.indexOf(input.charAt(input.length - 1));
//         if(lkey === 64){
//             return input.substring(0,input.length - 1);
//         }
//         return input;
//     },
//
//     decode: function (input, arrayBuffer) {
//         //get last chars to see if are valid
//         input = this.removePaddingChars(input);
//         input = this.removePaddingChars(input);
//
//         let bytes = parseInt((input.length / 4) * 3, 10);
//
//         let uarray;
//         let chr1, chr2, chr3;
//         let enc1, enc2, enc3, enc4;
//         let i = 0;
//         let j = 0;
//
//         if (arrayBuffer)
//             uarray = new Uint8Array(arrayBuffer);
//         else
//             uarray = new Uint8Array(bytes);
//
//         input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
//
//         for (i=0; i<bytes; i+=3) {
//             //get the 3 octects in 4 ascii chars
//             enc1 = this._keyStr.indexOf(input.charAt(j++));
//             enc2 = this._keyStr.indexOf(input.charAt(j++));
//             enc3 = this._keyStr.indexOf(input.charAt(j++));
//             enc4 = this._keyStr.indexOf(input.charAt(j++));
//
//             chr1 = (enc1 << 2) | (enc2 >> 4);
//             chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
//             chr3 = ((enc3 & 3) << 6) | enc4;
//
//             uarray[i] = chr1;
//             if (enc3 !== 64) uarray[i+1] = chr2;
//             if (enc4 !== 64) uarray[i+2] = chr3;
//         }
//
//         return uarray;
//     }
// }
const audioMap = new Map();
class AudioPlayer {
    constructor(mp3, ogg) {
        if (audioMap.has(mp3)) {
            this.audioBuffer = audioMap.get(mp3);
        } else {
            let load = (url, onerror) => {
                let request = new XMLHttpRequest();
                request.open("GET", url, true);
                request.responseType = "arraybuffer";
                request.onload = () => {
                    return audioContext.decodeAudioData(request.response, buffer => {
                        audioMap.set(mp3, buffer);
                        this.audioBuffer = buffer;
                    }, e => { onerror && onerror(e) });
                };
                request.onerror = e => { onerror && onerror(e) };
                request.send();
            };
            load(mp3, e => ogg ? load(ogg, e => console.log(e)) : console.log(e));
        }
        this.complete = [];
        this.paused = true;
        this.startTime = 0;
        this.volume = 1;
        this.timer = false;
    }
    get duration() {
        return this.audioBuffer ? this.audioBuffer.duration : 0;
    }
    get currentTime() {
        return this.startTime === 0 ? 0 : ((Date.now() - this.startTime) / 1000);
    }
    pause() {
        if (this.audioSource && !this.paused) {
            this.audioSource.stop();
            this.paused = true;
            if (this.timer) {
                clearTimeout(this.timer);
                this.timer = false;
            }
        }
    }
    play(second) {
        if (!this.audioBuffer) return;
        if (typeof second === 'undefined' && !this.paused) return;
        second = second || 0;
        this.pause();
        let audioSource = audioContext.createBufferSource();
        audioSource.buffer = this.audioBuffer;
        if (this.volume === 1) {
            audioSource.connect(audioContext.destination);
        } else {
            let gainNode = audioContext.createGain();
            gainNode.gain.value = this.volume;
            audioSource.connect(gainNode);
            gainNode.connect(audioContext.destination);
        }
        this.audioSource = audioSource;
        this.audioSource.start(0, second);
        this.paused = false;
        this.startTime = Date.now() - Math.floor(second * 1000);
        if (this.duration !== 0) {
            this.timer = setTimeout(() => {
                this.complete.forEach(complete => complete());
                this.paused = true;
            }, Math.ceil((this.duration - second) * 1000));
        }
    }
    onComplete(callback) {
        this.complete.push(callback);
    }
    offComplete(callback) {
        this.complete = callback ? this.complete.filter(c => c !== callback) : [];
    }
}

const utils = {
    getAudio(mp3, ogg) {
        return new AudioPlayer(mp3, ogg);
    },
    get touchable() {
        return navigator.maxTouchPoints > 0 || /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent);
    },
    get vw() {
        return parseInt($(window).width());
    },
    get vh() {
        return parseInt($(window).height());
    },
    get vmin() {
        return Math.min(this.vw, this.vh);
    },
    get vmax() {
        return Math.max(this.vw, this.vh);
    },
    isPortrait() {
        return this.vw < this.vh;
    },
    isLandscape() {
        return this.vw >= this.vh;
    },
    point(event, target, index = 0) {
        if (!isNaN(target)) {
            index = target;
            target = $(event.target);
        } else {
            target = $(target || event.target);
        }
        let touch = getTouch(event, target, index);
        let result = {
            clientX: touch.clientX,
            clientY: touch.clientY,
            pageX: touch.pageX,
            pageY: touch.pageY,
            identifier: touch.identifier || 0
        };
        let offset = target.offset();
        result.x = result.pageX - offset.left;
        result.y = result.pageY - offset.top;
        return result;
    },
    units: {
        rad: 'rad',
        turn: 'turn',
        deg: 'deg',
        grad: 'grad',

        px: 'px',
        pt: 'pt',
        in: 'in',
        cm: 'cm',
        mm: 'mm',
        vw: 'vw',
        vh: 'vh',
        vmin: 'vmin',
        vmax: 'vmax',

        s: 's',
        ms: 'ms'
    },
    asPx(value, unit = 'px') {
        unit = (/(px|pt|in|cm|mm|vw|vh|vmin|vmax)$/i.exec(value) || [ unit ])[0];
        value = parseFloat(value);
        switch (unit.toLowerCase()) {
            case 'px': return value;
            case 'pt': return value * 4 / 3;
            case 'in': return value * 96;
            case 'cm': return value * 48 / 1.27;
            case 'mm': return value * 48 / 12.7;
            case 'vw': return value * this.vw / 100;
            case 'vh': return value * this.vh / 100;
            case 'vmin': return value * this.vmin / 100;
            case 'vmax': return value * this.vmax / 100;
        }
    },
    asPt(value, unit = 'px') {
        return this.asPx(value, unit) * 3 / 4;
    },
    asIn(value, unit = 'px') {
        return this.asPx(value, unit) / 96;
    },
    asCm(value, unit = 'px') {
        return this.asPx(value, unit) * 1.27 / 48;
    },
    asMm(value, unit = 'px') {
        return this.asPx(value, unit) * 12.7 / 48;
    },
    asVm(value, unit = 'px') {
        return this.asPx(value, unit) * 100 / this.vm;
    },
    asVh(value, unit = 'px') {
        return this.asPx(value, unit) * 100 / this.vh;
    },
    asVmin(value, unit = 'px') {
        return this.asPx(value, unit) * 100 / this.vmin;
    },
    asVmax(value, unit = 'px') {
        return this.asPx(value, unit) * 100 / this.vmax;
    },
    asSecond(value, unit = 's') {
        unit = (/(s|ms)$/i.exec(value) || [ unit ])[0];
        value = parseFloat(value);
        switch (unit.toLowerCase()) {
            case 's' : return value;
            case 'ms' : return value / 1000;
        }
    },
    asMillisecond(value, unit = 's') {
        return this.asSecond(value, unit) * 1000;
    },
    asRad(value, unit = 'rad') {
        unit = (/(deg|turn|grad|rad)$/i.exec(value) || [ unit ])[0];
        value = parseFloat(value);
        switch (unit.toLowerCase()) {
            case 'rad': return value;
            case 'turn': return value * (2 * Math.PI);
            case 'deg': return value / 180 * Math.PI;
            case 'grad': return value / 200 * Math.PI;
        }
        return value;
    },
    asDeg(value, unit = 'rad') {
        return this.asRad(value, unit) / Math.PI * 180;
    },
    asTurn(value, unit = 'rad') {
        return this.asRad(value, unit) / (Math.PI * 2);
    },
    asGrad(value, unit = 'rad') {
        return this.asRad(value, unit) / Math.PI * 200;
    },
    ...utilTools
};
const mixin = adapter && adapter.mixin || { };
const getKeyEventNames = type => {
    let result = { };
    for (let name of keyNames) {
        if (name) result[name] = getEventName(type, name);
    }
    return new Proxy(result, {
        get(target, property, receiver) {
            return Reflect.get(target, property, receiver) || `${ type }.${ property }`;
        }
    });
};
const getControlEventNames = type => {
    let result = { };
    for (let name of [ 'a', 'b', 'x', 'y' ]) {
        if (name) result[name] = getEventName(type, name);
    }
    return new Proxy(result, {
        get(target, property, receiver) {
            return Reflect.get(target, property, receiver) || `${ type }.${ property }`;
        }
    });
};
const events = {
    key: {
        start: getKeyEventNames('key.start'),
        end: getKeyEventNames('key.end'),
        press: getKeyEventNames('key.press'),
    },
    control: {
        start: getControlEventNames('control.start'),
        end: getControlEventNames('control.end'),
        press: getControlEventNames('control.press')
    }
};
export { mixin, utils, events, Vue };

export default (application, listeners, callback) => {
    const inBrowser = typeof window !== 'undefined';
    const UA = inBrowser && window.navigator.userAgent.toLowerCase();
    const isMobile = (UA && /iphone|ipad|ipod|ios|android/.test(UA));//是否为手机环境
    if (config.dev) {
        if (isMobile) {
            require(['@whinc/web-console'], WebConsole => {
                new WebConsole(config.webconsole || {});
            });
        } else {
            Vue.config.devtools = config.devtools;
        }
    }

    try {
        document.domain = (/(\d{1,2}|[01]\d{2}|2[0-4]\d{2}|25[0-5])(\.(\d{1,2}|[01]\d{2}|2[0-4]\d{2}|25[0-5])){3}/ig.test(location.hostname) ? location.hostname : (location.hostname.match(/[^.]+\.[^.]+$/ig) || [location.hostname])[0]);
    } catch (e) { }
    document.title = config.name;

    $(() => {
        let fullScreenTimer;
        function listenCallBack(){
            if(isMobile && utils.isLandscape()) {
                $('#scrollTipMask').css({ display: 'block' });
                $('html, body').css({ overflow: 'auto', 'touch-action': 'auto' });
                if( window.innerHeight === document.documentElement.clientHeight ){ //全屏并且加载完毕,则不显示div
                    $('#scrollTipMask').css({ display: 'none' });
                    $('html, body').css({ overflow: 'hidden', 'touch-action': 'none' });
                } else { //未全屏显示则把div显示出来
                    $('html, body').css({ overflow: 'auto', 'touch-action': 'auto' });
                    clearTimeout(fullScreenTimer);
                    fullScreenTimer = setTimeout(() => $(document).scrollTop(0), 40);
                }
            }
        }
        $(window).on('resize', listenCallBack).on('scroll', listenCallBack);
        listenCallBack();

        $(document).on('gesturestart', e => {
            if (e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('dblclick', e => {
            if (e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('touchmove', e => {
            if (!$(e.target).parents('.modal,.scrollable,.web-console,#scrollTipMask').length && e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('touchstart', e => {
            if (!$(e.target).parents('.modal,.scrollable,.web-console,#scrollTipMask').length && !$(e.target).is('input,select,textarea') && e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('touchend', e => {
            if (!$(e.target).parents('.modal,.scrollable,.web-console,#scrollTipMask').length && !$(e.target).is('input,select,textarea') && e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('hold', e => {
            if (e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('contextmenu', e => {
            if (e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        }).on('selectstart', e => {
            if (e.cancelable && !e.isDefaultPrevented()) e.preventDefault();
        });

        const storage = new Proxy({ }, {
            get(target, property, receiver) {
                if (property in target || typeof property === 'symbol') {
                    return Reflect.get(target, property, receiver);
                } else {
                    let value = localStorage.getItem(property);
                    return value && JSON.parse(value);
                }
            },
            set(target, property, value, receiver) {
                if (property in target || typeof property === 'symbol') {
                    Reflect.set(target, property, value, receiver);
                } else {
                    localStorage.setItem(property, JSON.stringify(value));
                }
                return true;
            }
        });

        const lazy = {
            queue: [],
            timer: false,
            exec(executor) {
                if (typeof executor !== 'undefined') {
                    this.queue.push(executor);
                }
                if (this.queue.length) {
                    clearTimeout(this.timer);
                    this.timer = setTimeout(() => this.execute(), 0);
                }
            },
            execute() {
                while (this.queue.length) {
                    this.queue.shift().call();
                }
            }
        };

        const socket = new Proxy(io(location.protocol + '//' + location.hostname + ':' + location.port, {
            reconnection: false,
            transports: ['websocket', 'polling']
        }), {
            get(target, property, receiver) {
                if (property === 'on') {
                    let on = Reflect.get(target, property, receiver);
                    return arg => {
                        if (typeof arg === 'object' && arg !== null) {
                            for (let key in arg) {
                                if (arg.hasOwnProperty(key)) {
                                    on.call(target, key, data => lazy.exec(arg[key].bind(target, data)));
                                }
                            }
                        } else {
                            on.apply(target, arguments);
                        }
                    }
                } else {
                    return Reflect.get(target, property, receiver);
                }
            }
        });

        const queue = {
            queue: [],
            timer: false,
            exec(executor) {
                if (typeof executor !== 'undefined') {
                    this.queue.push(executor);
                }
                this.doExecute();
            },
            doExecute() {
                clearTimeout(this.timer);
                if (this.queue.length) {
                    this.timer = setTimeout(() => {
                        this.queue.shift().call();
                        this.doExecute();
                    }, 0);
                }
            }
        };

        const emit = new Proxy({ }, {
            get(target, property, receiver) {
                if (property in target) {
                    return Reflect.get(target, property, receiver);
                } else {
                    return data => queue.exec(() => socket.emit(property.replace(/^[_$]/i, ''), data));
                }
            }
        });

        const game = {
            data: {
                loading: true,

                player: { },
                rooms: {
                    loading: false,
                    page: 0,
                    list: [],
                    status: {
                        waiting: '等待中',
                        gaming: '游戏中',
                        sorting: '准备中'
                    }
                },

                dialog: {
                    alert: false,
                    settings: false,
                    join: false,
                    player: false,
                    login: false
                },
                tip: {
                    more: false
                },
                modal: {
                    introduction: false,
                    qr: false
                },

                config: clone(config)
            },
            computed: {
                qr: function() {
                    let url = window.location.origin + window.location.pathname + (this.player && this.player.room ? '#' + this.player.room.id : '');
                    let qr = qrcode(0, 'L');
                    qr.addData(url);
                    qr.make();
                    return qr.createDataURL(10, 15);
                    // return 'http://pan.baidu.com/share/qrcode?w=150&h=150&url=' + encodeURIComponent(window.location.origin + window.location.pathname + '#' + this.player.room.id)
                },
                dialogs: function() {
                    return Object.values(this.dialog).some(dialog => !!dialog);
                }
            },
            mounted() {
                if (this.config.login) {
                    this.loading = false;
                    this.dialog.login = {
                        id: storage.player && storage.player.id || '', password: ''
                    }
                } else {
                    this.loading = true;
                    if (storage.player) {
                        this.player = storage.player;
                        setTimeout(() => {
                            emit.login(this.player);
                        }, 0);
                    } else {
                        $.get(['id']).then(data => {
                            this.player.id = data;
                            emit.login(this.player);
                        }).catch(() => {
                            setTimeout(() => window.location.reload(), 500);
                        });
                    }
                }
                this.getRooms();
            },
            methods: {
                showControl() {
                    this.$control().show();
                },
                hideControl() {
                    this.$control().hide();
                },
                random(callback) {
                    let name = this.dialog.player.name;
                    $.get(['random']).then(data => {
                        if (name === this.dialog.player.name) {
                            this.dialog.player.name = data;
                            callback && typeof callback === 'function' && callback();
                        }
                    });
                },
                doLogin() {
                    if (!this.dialog.login.id) this.alert('请输入用户名');
                    else if (!this.dialog.login.password) this.alert('请输入密码');
                    else emit.login(this.player = this.dialog.login);
                },
                help() {
                    this.modal.introduction = true;
                },
                mine() {
                    this.dialog.player = { id: this.player.id, name: this.player.name }
                },
                share() {
                    this.modal.qr = true;
                },
                settings() {
                    this.dialog.settings = this.room.settings;
                },
                more() {
                    this.tip.more = !this.tip.more;
                },
                home() {
                    window.location.href = this.config.home;
                },
                getRooms: function() {
                    if (!this.rooms.loading) {
                        this.rooms.loading = true;
                        emit.rooms();
                    }
                },
                alert(message) {
                    this.dialog.alert = message;
                },
                prepare() {
                    this.loading = true;
                    emit.settings();
                },
                create() {
                    this.loading = true;
                    emit.create(this.dialog.settings);
                    this.dialog.settings = false;
                },
                preJoin(roomId) {
                    this.dialog.join = { roomId: roomId && !isNaN(roomId) ? roomId : '' };
                },
                join() {
                    if (this.dialog.join && this.dialog.join.roomId && !isNaN(this.dialog.join.roomId)) {
                        this.loading = true;
                        emit.join(this.dialog.join);
                        this.dialog.join = false;
                    }
                },
                joinTo(roomId) {
                    this.preJoin(roomId);
                    this.join();
                },
                exit() {
                    this.loading = true;
                    emit.exit();
                },
                start() {
                    if (this.player.room.canStart && this.player.room.creator.id === this.player.id) {
                        this.loading = true;
                        emit.start();
                    }
                },
                move(role, team, index) {
                    let data = { team, index };
                    if (role === true) {
                        this.loading = true;
                        if (this.creator) {
                            emit.disable(data);
                        } else {
                            emit.move(data);
                        }
                    } else if (role === false) {
                        if (this.creator) {
                            this.loading = true;
                            emit.enable(data);
                        }
                    } else if (role.player.id !== this.player.id) {
                        if (this.creator) {
                            if(confirm('是否踢出玩家 ' + role.player.name)) {
                                this.loading = true;
                                emit.kick(data);
                            }
                        }
                    }
                },
                confirm() {
                    this.loading = true;
                    emit.confirm();
                },
                savePlayer() {
                    this.loading = true;
                    if (!this.dialog.player.name) {
                        this.random(() => this.savePlayer());
                    } else {
                        emit.info(this.dialog.player);
                        this.dialog.player = false;
                    }
                },
                onControl(control) {
                    this.$emit('control', control);
                },
                $game: function() {
                    return this.$refs.game;
                },
                $control: function() {
                    return this.$refs.control;
                }
            }
        };

        if (!application) application = () => ({ });
        if ($.isFunction(application)) application = application({ socket, emit, storage, set, append }) || { };
        if (!adapter || !adapter.create) throw '无法初始化';
        const app = adapter.create(application, game);
        app.socket = socket;
        app.emit = emit;
        app.storage = storage;

        socket.on({
            player(player) {
                set(app, 'player', player);
                let roomId = parseInt((window.location.hash || '').replace(/^.*?#/ig, '').replace(/[^\d].+/ig, ''));
                if (roomId && !isNaN(roomId)) {
                    if (app.room) {
                        window.location.href = window.location.origin + window.location.pathname;
                    } else {
                        app.joinTo(roomId.toString());
                    }
                }
                if (!app.room) app.getRooms();
                app.loading = false;
                storage.player = { id: app.player.id, name: app.player.name };
                app.dialog.login = false;
                if (!app.player.name) {
                    app.dialog.player = { id: app.player.id, name: app.player.name };
                }
            },
            rooms(rooms) {
                app.rooms.list = rooms.page === 0 ? rooms.list : app.rooms.list.concat(rooms.list);
                app.rooms.page = rooms.page;
                app.rooms.loading = false;
                if (app.rooms.list.length === 0) {
                    setTimeout(() => app.getRooms(), 1000);
                }
            },
            role(role) {
                set(app.player, 'role', role);
                app.loading = false;
            },
            room(room) {
                set(app.player, 'room', room);
                if (app.game) emit.ready();
                app.loading = false;
            },
            game(game) {
                app.room && set(app.room, 'game', game);
                app.loading = false;
            },
            group(summary) {
                app.game && app.game.groups && set(app.game.groups, summary.index.toString(), summary);
                app.loading = false;
            },
            board(board) {
                app.game && set(app.game, 'board', board);
            },
            message(message) {
                if (app.game) {
                    append(app.game.messages, message);
                } else if (app.room) {
                    append(app.room.messages, message);
                }
            },
            settings(settings) {
                app.dialog.settings = settings;
                app.loading = false;
            },
            err(err) {
                err ? app ? app.alert(err) : alert(err) : window.location.reload();
                app && (app.loading = false);
            },
            disconnect() {
                app.loading = true;
                let retryTimes = 0;
                let retry = function() {
                    retryTimes++;
                    $.get(['id']).then(function() {
                        window.location.reload();
                    }).catch(function() {
                        setTimeout(retry, Math.min(retryTimes, 10) * 500);
                    });
                };
                retry();
            }
        });
        if (!listeners) listeners = () => ({ });
        if ($.isFunction(listeners)) listeners = listeners({ socket, emit, app, storage, set, append }) || { };
        socket.on(listeners);
        emit.connection();

        window.alert = function(message) {
            if (app.view) {
                app.view.alert = message;
            } else if (app.dialog) {
                app.dialog.alert = message;
            }
        };
        $(document).on('keypress', event => app.$emit('key.press', { code: event.keyCode, name: keyNames[event.keyCode] || event.key || '', shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey }));
        $(document).on('keydown', event => {
            app.$emit('key.start.repeat', { code: event.keyCode, name: keyNames[event.keyCode] || event.key || '', shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey });
            if (!keyStates[event.keyCode]) {
                keyStates[event.keyCode] = true;
                app.$emit('key.start', { code: event.keyCode, name: keyNames[event.keyCode] || event.key || '', shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey });
            }
        });
        $(document).on('keyup', event => {
            keyStates[event.keyCode] = false;
            app.$emit('key.end', { code: event.keyCode, name: keyNames[event.keyCode] || event.key || '', shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey });
        });
        $(window).on('resize', event => app.$emit('resize'));

        callback && callback();
    });
};
